home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
C/C++ Users Group Library 1996 July
/
C-C++ Users Group Library July 1996.iso
/
listings
/
v_11_08
/
twilling
/
jd2greg.c
< prev
Wrap
Text File
|
1993-02-23
|
17KB
|
376 lines
/**********************( GREGORIAN CALENDAR MODULE )**************************
Routines for converting between Gregorian Calendar dates and Julian Days, for
validating date input, for date arithmetic, and for learning day of the week
and of the year.
(c) 1991, 1993 by Bob Twilling.
C Users Journal readers may use this code for any purpose.
/-------------------( Export to header file "JD2GREG.H" )--------------------*/
typedef struct { short mo;
short dy;
short yr; } MDY;
extern MDY * jd2greg( long /*jd*/, MDY * /*date*/ );
extern long greg2jd( short /*month*/, short /*day*/ , short /*year*/ );
extern long ValidateDate(short /*month*/, short /*day*/ , short /*year*/ );
extern long DDays( MDY * /*date1*/, MDY * /*date2*/ );
extern MDY * DatePlus( MDY * /*date*/ , long /*ddays*/, MDY * /*newdt*/ );
extern int DayOfWeek( short /*month*/, short /*day*/ , short /*year*/ ,
char * /*name*/ );
extern int DayOfYear( short /*month*/, short /*day*/ , short /*year*/ );
/*=======================( MONTH TO DAY-OF-YEAR )===========================/
Given a month, calc day of year up until. This is a macro used by the next
two functions, see discussion below for the logic. Assumes rectified years
(March is month 1). Can change the #if to 0 to save 24 bytes of near heap
at a slight speed cost.
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#if 1
static short m2doy[] =
{ 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337 };
#define M2DOY(mo) (m2doy[mo-1])
#else
#define M2DOY(mo) ((((mo) * 979) >> 5) - 30)
#endif
/*=========================( GREGORIAN TO JD )==============================/
Given a month, day, and year, returns a long integer -- the Julian Day number
at noon of that date.
ALGORITHM:
1> Rectify the date so that the year begins on March 1. This simplifies
calculations by putting oddball leapdays last.
2> Now calculate the day of year, e.g. Oct 28th is day number 242, Jan 3rd
is day number 309 (of the previous year! See above.) A lookup table is
the fastest way to account for the varied number of days in a month, at
a cost of 24 bytes of near heap space. Or we can use the formula:
doy = ((mo * 979) / 32) - 30 + dy;
March is the first month, remember. There's no theory behind these magic
numbers -- a lot of other ones work too, but I like my 32 (a nice round
number). 979/32, or 367/12, or 30.6, all equal about the average days
per month, ignoring February. The magic of integer arithmetic, and the
lucky fact that our 30-day months are well distributed, does the rest.
3> Compute the number of days up to the beginning of the year using the
simple Gregorian formula:
yrdays = (365 * yr) + (yr / 4) - (yr / 100) + (yr / 400);
There are some nice round numbers in this formula too, begging to be
replaced by shifts.
4> Add the result of the last two steps. Then add a constant for an origin
transform to the Julian Day system.
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
extern long greg2jd( short mo, short dy, short yr ) {
short lp;
if ((mo -= 2) <= 0) { mo += 12; yr--; } // 1> Roll-under the date
dy += M2DOY(mo); // 2> Calc day of year
lp = yr >> 2; // 3> Add and subtract leap days
dy += lp;
lp /= 25;
dy -= lp;
dy += lp >> 2;
return (long)yr * 365 + dy + 1721119; } // 4> One div, one mul!
/*=========================( JD TO GREGORIAN )==============================/
Given a long Julian Day and a pointer to a month-day-year structure, fills in
that structure with the calendar date and returns the same pointer to it.
ALGORITHM:
1> Transform the origin of the Julian Day to the start of some 400-year
period. Because we are assuming a JD valid in the Gregorian Calendar we
subtract 2305507, so that day 1 is 1-Mar-1600. As the routine is currently
written this gives an algorithm valid from 1200 AD.
2> Subtract or divide out 400-year (146097 day) blocks, then 100-year (36524
day) blocks, noting the number of blocks removed. Since 16-bit machines
and/or compilers do long division like paint dries, we use successive
subtraction -- sort of. Actually, we subtract too far, then add back, for
a slight speed gain. When we migrate to 32-bits, rewrite this part for
speed and comprehensibility.
3> Divide out 4-year (1461 day) and 1-year (365 day) blocks. Calculate the
calendar year from the number and size of blocks removed; the remainder is
the day of the year. If the Julian Day represented a Feb 29, the algorithm
fails here because 1461/365 equals more than four. Check and adjust for
this special case.
4> Calculate the month of the year from the magic-number formula:
mo = ((doy + 30) * 32) / 979;
Surprisingly, this is faster on a 386 than scanning a twelve member table.
Compute the day of the month by subtracting the days up until that month
using either the table or the formula declared in M2DOY() above.
5> Roll over the month and year if we found a date in January or February.
6> Be careful when changing this code: 146097/36524 and 1461/365 both equal
4, which blows up the algorithm on JD's representing Feb 29's. As written,
we avoid the first by a tricky origin shift, the second by explicitly
limiting 1461/365 to a max of three.
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include <stdlib.h> //for div()
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
extern MDY * jd2greg( long jd, MDY * date ) {
unsigned short x, y, d;
div_t sd;
y = 1600; jd -= 2305507; // 1> new origin 0 = 2/29/1600
while (jd > 0) { y += 400; jd -= 146097; } // 2> lop off 400-yr blocks
do { y -= 100; } while ((jd += 36524) < 0); // and add back 100-yr ones
d = (unsigned short)jd; // 3> within 16-bit range now
x = d / 1461; // pity ain't no ANSI udiv()
y += x << 2; // note 4-yr blocks
d -= x * 1461; // and lop them off
sd = div( d, 365); // does % and / in same op
date->yr = y + sd.quot; // got years, provisionally
d = ++sd.rem; // 4> day-of-year (base 1)
if (sd.quot == 4) { date->yr--; d = 366; } // case we hit a leap-year
x = ((d + 30) << 5) / 979; // x = month, March is 1
date->dy = d - M2DOY(x); // got day of month
if ((x += 2) > 12) { date->yr++; x -= 12; } // 5> roll around Jan and Feb
date->mo = x; // got month
return(date); }
/*==========================( VALIDATE DATE )===============================/
Given a month, day, and year, returns a positive long integer representing the
corresponding Julian Day at 12h UTC, unless the passed date is invalid in the
Gregorian Calendar, in which case returns zero. This somewhat-boolean retval
will save the user another call to greg2jd() if the date does prove valid.
ALGORITHM:
1> Convert the passed Gregorian date into its Julian Day.
2> Check that the date is not earlier than 15 Oct 1582, the first day
Gregory's calendar was in use anywhere. An earlier date would indicate
that the caller should have used Julian Calendar (Old Style) conversion
routines.
3> Check that the date is not later than 28 Feb 4000. On somewhat shaky
ground here. If our planet's orbital speed doesn't change much, Greg's
calendar will lose a day every 3300 years. I read somewhere of a proposal
for a Rev